package com.wxm.common.file.core.service;

import com.alibaba.fastjson.JSONObject;
import com.wxm.common.file.core.constant.YesNoConstant;
import com.wxm.common.file.core.enm.FileTypeEnum;
import com.wxm.common.file.core.exception.FileException;
import com.wxm.common.file.core.result.DeleteFileResult;
import com.wxm.common.file.core.result.UploadFileResult;
import com.wxm.common.file.core.result.UploadMd5Result;
import com.wxm.common.file.core.service.supplier.SupplierBaseService;
import com.wxm.common.file.core.util.FileUtils;
import com.wxm.common.file.core.util.MD5Encrypt;
import com.wxm.common.file.core.util.StringUtils;
import com.wxm.common.file.core.config.UploadConfigService;
import com.wxm.common.file.core.config.UploadConfigVo;
import com.wxm.common.file.core.property.UploadBaseProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * <p>
 *      上传Service
 * </p>
 *
 * @author: wxm
 * @Date: 2022/1/17
 */
public abstract class UploadBaseService {
    @Autowired
    private UploadConfigService uploadConfigService;
    @Autowired
    private ApplicationContext applicationContext;
    /**
     * 获取文件的类型
     * @return FileTypeEnum
     */
    protected abstract FileTypeEnum fileType();

    /**
     * 文件上传(MultipartFile)
     * @param userId 上传用户ID
     * @param uploadParam 自定义参数
     * @param multipartFile 上传文件
     * @param hierarchy 层级->用于拼装路径
     * @return UploadFileResult
     */
    public UploadFileResult upload(Integer userId, JSONObject uploadParam, MultipartFile multipartFile, String... hierarchy){
        if (multipartFile == null) {
            FileException.error("上传失败 —> 文件缺失");
        }
        try {
            return this.upload(userId,uploadParam,multipartFile.getInputStream(),multipartFile.getOriginalFilename(),multipartFile.getSize(),hierarchy);
        } catch (IOException e) {
            FileException.error("上传失败 —> 获取InputStream失败");
        }
        return null;
    }

    /**
     * 文件上传（文件path ->todo 注意：本地文件直接上传，网络链接得保存到本地在进行上传）
     * @param userId 上传用户ID
     * @param uploadParam 自定义参数
     * @param filePath 上传文件路径
     * @param hierarchy 层级->用于拼装路径
     * @return UploadFileResult
     */
    public UploadFileResult upload(Integer userId, JSONObject uploadParam,String filePath,String... hierarchy) {
        if (filePath == null) {
            FileException.error("上传失败 —> 文件路径缺失");
        }
        try {
            File file = new File(filePath);
            if (!file.exists()){//非本地图片 得创建本地文件在进行上传
                file = FileUtils.urlToFile(filePath);
            }
            return this.upload(userId,uploadParam, new FileInputStream(file), file.getName(), file.length(), hierarchy);
        } catch (FileNotFoundException e) {
            FileException.error("上传失败 —> 创建FileInputStream失败");
        }
        return null;
    }

    /**
     * 文件上传（字节数组）
     * @param userId 上传用户ID
     * @param uploadParam 自定义参数
     * @param uploadBytes 上传字节数组
     * @param fileName 文件名称
     * @param hierarchy 层级->用于拼装路径
     * @return UploadFileResult
     */
    public UploadFileResult upload(Integer userId, JSONObject uploadParam,byte[] uploadBytes,String fileName,String... hierarchy){
        if (uploadBytes == null){
            FileException.error("上传失败 —> 文件字节缺失");
        }
        if (fileName == null){
            FileException.error("上传失败 —> 文件名称缺失");
        }
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(uploadBytes);
        return this.upload(userId,uploadParam, byteArrayInputStream, fileName, (long) uploadBytes.length, hierarchy);
    }

    /**
     * 文件上传（File）
     * @param userId 上传用户ID
     * @param uploadParam 自定义参数
     * @param file 上传文件
     * @param hierarchy 层级->用于拼装路径
     * @return UploadFileResult
     */
    public UploadFileResult upload(Integer userId, JSONObject uploadParam,File file, String... hierarchy){
        if (file == null){
            FileException.error("上传失败 —> 文件缺失");
        }
        try {
            return this.upload(userId,uploadParam,new FileInputStream(file),file.getName(),file.length(),hierarchy);
        } catch (FileNotFoundException e) {
            FileException.error("上传失败 —> 创建FileInputStream失败");
        }
        return null;
    }

    /**
     * 文件上传（InputStream）
     * @param userId 上传用户ID
     * @param uploadParam 自定义参数
     * @param inputStream InputStream
     * @param fileName 文件名称
     * @param hierarchy 层级->用于拼装路径
     * @return UploadFileResult
     */
    public UploadFileResult upload(Integer userId, JSONObject uploadParam, InputStream inputStream,String fileName,String... hierarchy) {
        if (inputStream == null){
            FileException.error("上传失败 —> 文件InputStream缺失");
        }
        if (fileName == null){
            FileException.error("上传失败 —> 文件名称缺失");
        }
        try {
            return this.upload(userId,uploadParam,inputStream,fileName, (long) inputStream.available(),hierarchy);
        }catch (IOException e){
            FileException.error("上传失败 —> 读取InputStreamSize失败");
        }
        return null;
    }

    /**
     * 上传（核心实现逻辑）
     * @param userId 上传用户ID
     * @param uploadParam 自定义参数
     * @param inputStream InputStream
     * @param fileName 文件名称
     * @param size 文件大小
     * @param hierarchy 层级->用于拼装路径
     * @return UploadFileResult
     */
    private UploadFileResult upload(Integer userId, JSONObject uploadParam,InputStream inputStream,String fileName,Long size,String... hierarchy){
        //校验名称得到后缀
        String suffix = this.checkFileType(fileName);
        //得到配置Vo
        UploadConfigVo configVo = uploadConfigService.getUploadConfigVo(uploadParam);
        //校验是否超出最大上传限制
        this.checkFileSize(fileName,size,configVo.getMaxSize());
        //是否生成新的文件名称
        String fileNamePath = this.checkCreatNowFileName(YesNoConstant.YES.equals(configVo.getRenTag()), fileName);
        //计算层级
        String hierarchyPath = String.join("/", hierarchy);
        //拼接层级和文件名称
        fileNamePath =  hierarchyPath + "/" + fileNamePath;

        //获取到上传配置
        UploadBaseProperty uploadBaseProperty = configVo.getUploadBaseProperty();
        SupplierBaseService supplierBaseService = applicationContext.getBean(uploadBaseProperty.supplierService());
        supplierBaseService.init(uploadBaseProperty);

        String fileMd5 = "";
        UploadMd5Result md5Result = null;
        byte[] bytes = null;
        //如果需要对文件内容MD5加密校验
        if (uploadConfigService.checkFileMd5()){
            try {
                //读取inputStream的内容
                bytes = FileUtils.cloneInputStream(inputStream).toByteArray();
                fileMd5 = MD5Encrypt.encrypt(Arrays.toString(bytes));
            } catch (Exception e) {
                FileException.error("上传失败 —> 文件内容MD5加密失败" + fileName);
            }
            md5Result = uploadConfigService.doCheckFileMd5(fileMd5,uploadParam);
        }

        //获取到原来的文件地址 就直接返回
        if (md5Result != null){
            UploadFileResult uploadFileResult = new UploadFileResult();
            uploadFileResult.setRootPath(md5Result.getRootPath());
            uploadFileResult.setAbsolutePath(md5Result.getAbsolutePath());
            uploadFileResult.setRelativePath(md5Result.getRelativePath());
            uploadFileResult.setAccessPath(md5Result.getAccessPath());
            //文件MD5Eq匹配上后的操作
            Consumer<UploadMd5Result> afterConsumer = this.uploadConfigService.md5EqAfter();
            if (afterConsumer != null){
                afterConsumer.accept(md5Result);
            }
            return uploadFileResult;
        }else {
            try {
                //bytes不为null说明被读取过了 生成新的ByteArrayInputStream
                supplierBaseService.uploading(bytes != null ? new ByteArrayInputStream(bytes) : inputStream,fileNamePath);
                inputStream.close();
            }catch (Exception e){
                FileException.error("上传失败 —> " + fileName + " " + e.getMessage());
            }
        }

        UploadFileResult uploadFileResult = new UploadFileResult();
        uploadFileResult.setFileType(this.fileType() == null ? null : this.fileType().getType());
        uploadFileResult.setFileSupplier(uploadBaseProperty.fileSupplierEnum().getType());
        uploadFileResult.setFileOldName(fileName);
        uploadFileResult.setFileNowName(fileName);
        uploadFileResult.setSize(size);
        uploadFileResult.setSuffix(suffix);
        uploadFileResult.setRootPath(uploadBaseProperty.getRootPath());
        uploadFileResult.setRelativePath(hierarchyPath);
        uploadFileResult.setAbsolutePath(fileNamePath);
        uploadFileResult.setAccessPath(uploadBaseProperty.getRootPath() + "/" + fileNamePath);
        uploadFileResult.setFileMd5(fileMd5);
        uploadFileResult.setUserId(userId);
        uploadFileResult.setUploadTime(LocalDateTime.now());

        //如果配置了上传后的操作 进行执行
        Consumer<UploadFileResult> afterConsumer = this.uploadConfigService.uploadAfter(uploadParam);
        if (afterConsumer != null){
            afterConsumer.accept(uploadFileResult);
        }
        return uploadFileResult;
    }

    /**
     * 文件删除
     * @param userId 删除用户ID
     * @param deleteParam 自定义参数
     * @param filePath 要删除文件的访问路径
     * @return DeleteFileResult
     */
    public DeleteFileResult delete(Integer userId, JSONObject deleteParam, String filePath){
        //得到配置Vo
        UploadConfigVo configVo = uploadConfigService.getUploadConfigVo(deleteParam);
        UploadBaseProperty uploadBaseProperty = configVo.getUploadBaseProperty();
        SupplierBaseService supplierBaseService = applicationContext.getBean(uploadBaseProperty.supplierService());
        supplierBaseService.init(uploadBaseProperty);
        try {
            supplierBaseService.delete(filePath);
        }catch (Exception e){
            //不需要强制执行deleteAfter 就直接报错
            if (YesNoConstant.NO.equals(configVo.getDeleteAfter())) {
                FileException.error("上传删除 —> " + filePath + " " + e.getMessage());
            }
        }

        DeleteFileResult deleteFileResult = new DeleteFileResult();
        deleteFileResult.setFileType(this.fileType() == null ? null : this.fileType().getType());
        deleteFileResult.setFileSupplier(uploadBaseProperty.fileSupplierEnum().getType());
        deleteFileResult.setAccessPath(filePath);
        deleteFileResult.setUserId(userId);
        deleteFileResult.setDeleteTime(LocalDateTime.now());

        //如果配置了删除后的操作 进行执行
        //如果配置了删除后的操作 进行执行
        Function<JSONObject,Object> deleteAfter = this.uploadConfigService.deleteAfter();
        if (deleteAfter != null){
            Object key = deleteAfter.apply(deleteParam);
            deleteFileResult.setKey(key);
        }
        return deleteFileResult;
    }

    /**
     * 校验文件类型是否正确
     * @param fileName 文件名称
     * @return 文件后缀
     */
    private String checkFileType(String fileName){
        FileTypeEnum fileTypeEnum = this.fileType();
        String suffix = fileName.substring(fileName.indexOf("."));
        if (fileTypeEnum != null) {
            //将名称拆分
            List<String> list = Arrays.asList(fileTypeEnum.getSuffix());
            if (!list.contains(suffix)) {
                FileException.error(fileTypeEnum.name() + " 不支持的文件类型：" + suffix);
            }
        }
        return suffix;
    }

    /**
     * 校验文件大小是否超长
     * @param fileName 文件名称
     * @param fileSize 文件大小
     * @param maxSize 最大长度
     */
    private void checkFileSize(String fileName,Long fileSize,Long maxSize){
        if (fileSize > maxSize){
            FileException.error(fileName + " [" + fileSize + "]Byte 已超出最大上传限制[" + maxSize + "]Byte");
        }
    }

    /**
     * 创建新的文件名称
     * @param tag 是否拼接UUID
     * @param fileName 文件名称
     * @return String
     */
    private String checkCreatNowFileName(Boolean tag, String fileName){
        if (tag){
            String[] split = fileName.split("\\.");
            return split[0] + "-" + UUID.randomUUID().toString().replace("-","") + "." + split[1];
        }
        return fileName;
    }
}
